/*  PrimeBlaster
    by Kjetil Eide 
    kjetile445@gmail.com

    This file is part of PrimeBlaster

    PrimeBlaster is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    PrimeBlaster is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PrimeBlaster.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.eide.spaceship;

import java.util.Stack;
import java.lang.Math;

import org.newdawn.slick.Image;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.geom.Polygon;

/**
 * Simulates a rigid body and computes rotation/movement of body
 * according to forces acting on body. All coordinates are in 
 * screen coordinates. Origo in top left corner of screen.
 *  
 * @author kjetil
 *
 */
public abstract class RigidBody {	
	/* body shape with center in (0,0) and orientation 0 */
	private Polygon bodyShape;
	/* body shape translated and rotated */
	private Polygon bodyShapeOriented;
	
	private float density;
	private float mass;
	private float coefficientOfRestitution;
	private float width;
	private float height;
	private float orientation;
	private float torque;
	private float angVelocity;
	private float angAccel;
	private Vector2f pos; /* cm position */
	private Vector2f velocity; /* cm velocity */
	private Vector2f accel; /* cm accel */
	private Vector2f cmForce;
	private float cmMomentOfInertia;
	private Stack<Vector2f> force;
	private Stack<Vector2f> forceRadius; /* radius from CM to engine point */
	
	
	RigidBody(float density, float width, float heigth,
			float coefficientOfRestitution, Vector2f pos, float[] points) {
		this.density = density;
		setDimensions(width, heigth);
		this.coefficientOfRestitution = coefficientOfRestitution;
		this.width = width;
		this.height = heigth;
		torque = 0;
		angAccel = 0;
		angVelocity = 0;
		orientation = 0;
		this.pos = pos; /* position of center of mass. */
		velocity = new Vector2f(0,0);
		accel = new Vector2f(0,0);
		cmForce = new Vector2f(0,0);
		setShape(points);
		force = new Stack<Vector2f>();
		forceRadius = new Stack<Vector2f>();		
	}
	
	/* Update the polygon outline of the body according to orientation and 
	 * position of CM */
	public void translate() {
		Transform rotation = Transform.createRotateTransform(orientation);
		Transform translation = Transform.createTranslateTransform(pos.x, pos.y);
		bodyShapeOriented = (Polygon) bodyShape.transform(rotation);
		bodyShapeOriented = (Polygon) bodyShapeOriented.transform(translation);
	
	}
	
	public void sumForces() {
		torque = 0;
		cmForce.set(0,0);
		while(!force.empty()) {
			addCMForce(force.pop(), forceRadius.pop());
		}
	}
	
	public void calcAccel() {
		/* accel = force / mass */
		accel.set(new Vector2f(cmForce.scale(1/mass)));
		angAccel = torque / cmMomentOfInertia;
	}
	
	public void addForce(Vector2f force, Vector2f forceRadius) {
		this.force.push(force);
		this.forceRadius.push(forceRadius);
	}
	
	public void addCMForce(Vector2f force, Vector2f radius) {
		cmForce.add(force);
		torque += VectorMath.perpDotProd(radius, force);
	}
	
	public void addTorque(float torque) {
		this.torque += torque;
	}
	
	public void addOrientation(float angle) {
		orientation += angle;
		if (orientation < 0) {
			orientation = (float) (2*Math.PI + orientation);
		}
		if (orientation > 2*Math.PI) {
			orientation = orientation - 2*(float)Math.PI;
		}	
	}
	
	public void addVelocity(Vector2f velocity) {
		if (isUnmovable())
			return;
		this.velocity.add(velocity);
	}
	
	public void addAngVelocity(float angVelocity) {
		if (isUnmovable())
			return;
		this.angVelocity += angVelocity;
	}
	
	/* Move the rigid body by the specified offset
	 * this is used for moving the body out of penetration
	 * after a collision
	 */
	public void addPos(Vector2f offset) {
		if (isUnmovable())
			return;
		pos.add(offset);
	}
	public void subPos(Vector2f offset) {
		if (isUnmovable())
			return;
		pos.sub(offset);
	}
	
	public void integrate(float deltaTime) {
		if (isUnmovable())
			return;
		velocity.add(new Vector2f(accel).scale(deltaTime));
		pos.add(new Vector2f(velocity).scale(deltaTime));		
		angVelocity += angAccel * deltaTime;
		addOrientation(angVelocity * deltaTime);
	}

	public void setVelocity(Vector2f velocity) {
		if (isUnmovable())
			return;
		this.velocity = velocity;
	}
	
	public void setAngVelocity(float angVelocity) {
		if (isUnmovable())
			return;
		this.angVelocity = angVelocity;
	}
	
	public void setDimensions(float width, float height) {
		this.width = width;
		this.height = height;
		mass = density * width * height;
		cmMomentOfInertia = (mass/12) * (width*width + height*height);
	}
	
	public void setShape(float[] points) {
		bodyShape = new Polygon(points);
		translate();
	}
	
	public void setOrientation(float orientation) {
		this.orientation = orientation;
	}
	
	public void setPos(float x, float y) {
		pos = new Vector2f(x, y);
	}
	
	/*
	 * Get methods
	 */
	
	public Vector2f getPos() {
		return pos;
	}
	
	public int getX() {
		return (int) pos.getX();
	}
	
	public int getY() {
		return (int) pos.getY();
	}
	
	public Vector2f getAccel() {
		return accel;
	}
	
	public Vector2f getVelocity() {
		return velocity;
	}
	
	public float getAngAccel() {
		return angAccel;
	}
	
	public float getAngVelocity() {
		return angVelocity;
	}
	
	public float getOrientation(char type) {
		if (type == 'R') {
			return orientation;
		}
		if (type == 'D') {
			return (orientation / (float)Math.PI) * 180.0f;
		}
		return -1f;
	}
	
	public Polygon getShape() {
		return bodyShapeOriented;
	}
	
	public float getWidth() {
		return width;
	}
	
	public float getHeigth() {
		return height;
	}
	
	public float getMass() {
		return mass;
	}
	
	public float getCoefficientOfRestitution() {
		return coefficientOfRestitution;
	}
	
	public float getMomentOfInertia() {
		return cmMomentOfInertia;
	}

	/* this should return a bitmap picture of
	 * the rigid body, i.e. a spaceship, asteroid, etc..
	 */
	public abstract Image getImage();
	
	public abstract boolean isUnmovable();
}
